package com.my.chapter21;

import net.mindview.util.Enums;

import java.util.*;
import java.util.concurrent.*;

public class practice36 {
    public static void main(String[] args) throws Exception {
        ExecutorService exec = Executors.newCachedThreadPool();
        Restaurants restaurant = new Restaurants(exec, 5, 2);
        exec.execute(restaurant);
        if(args.length > 0) // Optional argument
            TimeUnit.SECONDS.sleep(new Integer(args[0]));
        else {
            System.out.println("Press 'ENTER' to quit");
            System.in.read();
        }
        exec.shutdownNow();
    }
}

interface Food{
    enum Appetizer implements Food {
        SALAD,SOUP,SPRING_ROLLS;
    }
    enum MainCourse implements Food {
        LASAGNE,BURRITO,PAD_THAI,
        LENTILS,HUMMOUS,VINDALOO;
    }
    enum Dessert implements Food {
        TIRAMISU,GELATO,BLACK_FOREST_CAKE,
        FRUIT,CREME_CARAMEL;
    }
    enum Coffee implements Food {
        BLACK_COFFEE,DECAF_COFFEE,ESPRESSO,
        LATTE,CAPPUCCINO,TEA,HERB_TEA;
    }
}

//随机生成食物
enum Course{
    APPETIZER(Food.Appetizer.class),
    MAINCOURSE(Food.MainCourse.class),
    DESSERT(Food.Dessert.class),
    COFFEE(Food.Coffee.class);
    private Food[] values;
    private Course(Class<? extends Food> kind){
        values = kind.getEnumConstants();
    }
    public Food randomSelection(){
        return Enums.random(values);
    }
}

//保存一张桌子上所有的菜单
class OrderTicket {
    private static int counter;
    private final int id = counter++;
    private final Table table;
    private final List<Orders> orders =
            Collections.synchronizedList(new LinkedList<Orders>());
    public OrderTicket(Table table) { this.table = table; }
    public WaitPersons getWaitPerson() {
        return table.getWaitPerson();
    }
    public void placeOrder(Customers cust, Food food) {
        Orders order = new Orders(cust, food);
        orders.add(order);
        order.setOrderTicket(this);
    }
    public List<Orders> getOrders() { return orders; }
    public String toString() {
        StringBuilder sb = new StringBuilder(
                "Order Ticket: " + id + " for: " + table + "\n");
        synchronized(orders) {
            for(Orders order : orders)
                sb.append(order.toString() + "\n");
        }
        // Prune away the last added 'new-line' character
        return sb.substring(0, sb.length() - 1).toString();
    }
}

class Table implements Runnable {
    private static int counter;
    private final int id = counter++;
    private final WaitPersons waitPerson;
    private final List<Customers> customers;
    private final OrderTicket orderTicket =
            new OrderTicket(this);
    private final CyclicBarrier barrier;
    private final int nCustomers;
    private final ExecutorService exec;

    public Table(WaitPersons waitPerson, int nCustomers,
                 ExecutorService e) {
        this.waitPerson = waitPerson;
        customers = Collections.synchronizedList(
                new LinkedList<Customers>());
        this.nCustomers = nCustomers;
        exec = e;
        barrier = new CyclicBarrier(nCustomers + 1,
                new Runnable() {
                    public void run() {
                        System.out.println(orderTicket.toString());
                    }
                });
    }

    public WaitPersons getWaitPerson() { return waitPerson; }
    public void placeOrder(Customers cust, Food food) {
        orderTicket.placeOrder(cust, food);
    }
    public void run() {
        Customers customer;
        for(int i = 0; i < nCustomers; i++) {
            customers.add(customer = new Customers(this, barrier));
            exec.execute(customer);
        }
        try {
            barrier.await();//等待顾客坐满桌子
        } catch(InterruptedException ie) {
            System.out.println(this + " interrupted");
            return;
        } catch(BrokenBarrierException e) {
            throw new RuntimeException(e);
        }
        waitPerson.placeOrderTicket(orderTicket);
    }

    public String toString() {
        StringBuilder sb = new StringBuilder(
                "Table: " + id + " served by: " + waitPerson + "\n");
        synchronized(customers) {
            for(Customers customer : customers)
                sb.append(customer.toString() + "\n");
        }
        return sb.substring(0, sb.length() - 1).toString();
    }
}

class Orders {
    private static int counter;
    private final int id;
    private volatile OrderTicket orderTicket;
    private final Customers customer;
    private final Food food;
    public Orders(Customers cust, Food f) {
        customer = cust;
        food = f;
        synchronized(Orders.class) { id = counter++; }
    }
    void setOrderTicket(OrderTicket orderTicket) {
        this.orderTicket = orderTicket;
    }
    public OrderTicket getOrderTicket() {
        return orderTicket;
    }
    public Food item() { return food; }
    public Customers getCustomer() { return customer; }
    public String toString() {
        return "Order: " + id + " item: " + food +
                " for: " + customer;
    }
}

//厨师完成的菜
class Plate {
    private final Orders order;
    private final Food food;
    public Plate(Orders ord, Food f) {
        order = ord;
        food = f;
    }
    public Orders getOrder() { return order; }
    public Food getFood() { return food; }
    public String toString() { return food.toString(); }
}

class Customers implements Runnable {
    private static int counter;
    private final int id;
    private final CyclicBarrier barrier;
    private final Table table;
    private int nPlates;
    public Customers(Table table, CyclicBarrier barrier) {
        this.table = table;
        this.barrier = barrier;
        synchronized(Customers.class) { id = counter++; }
    }
    private final SynchronousQueue<Plate> placeSetting =
            new SynchronousQueue<Plate>();
    public void
    deliver(Plate p) throws InterruptedException {
        placeSetting.put(p);
    }
    public void run() {
        for(Course course : Course.values()) {
            Food food = course.randomSelection();
            table.placeOrder(this, food);
            ++nPlates;
        }
        try {
            barrier.await();
        } catch(InterruptedException ie) {
            System.out.println(this + " interrupted while ordering meal");
            return;
        } catch(BrokenBarrierException e) {
            throw new RuntimeException(e);
        }
        for(int i = 0; i < nPlates; i++)
            try {
                // Blocks until course has been delivered:
                System.out.println(this + "eating " + placeSetting.take());
            } catch(InterruptedException e) {
                System.out.println(this + "waiting for meal interrupted");
                return;
            }
        System.out.println(this + "finished meal, leaving");
    }

    public String toString() {
        return "Customer " + id + " ";
    }
}

class WaitPersons implements Runnable {
    private static int counter;
    private final int id = counter++;
    private final Restaurants restaurant;
    final BlockingQueue<Plate> filledOrders =
            new LinkedBlockingQueue<Plate>();
    public WaitPersons(Restaurants rest) { restaurant = rest; }
    public void placeOrderTicket(OrderTicket orderTicket) {
        try {
            restaurant.orderTickets.put(orderTicket);
        } catch(InterruptedException e) {
            System.out.println(this + " placeOrderTicket interrupted");
        }
    }
    public void run() {
        try {
            while(!Thread.interrupted()) {
                Plate plate = filledOrders.take();
                System.out.println(this + "received " + plate +
                        " delivering to " +
                        plate.getOrder().getCustomer());
                plate.getOrder().getCustomer().deliver(plate);
            }
        }catch(InterruptedException e) {
            System.out.println(this + " interrupted");
        }
        System.out.println(this + " off duty");
    }

    public String toString() {
        return "WaitPerson " + id + " ";
    }
}

class Chefs implements Runnable {
    private static int counter;
    private final int id = counter++;
    private final Restaurants restaurant;
    private static Random rand = new Random(47);
    public Chefs(Restaurants rest) { restaurant = rest; }
    public void run() {
        try {
            while(!Thread.interrupted()) {
                OrderTicket orderTicket =
                        restaurant.orderTickets.take();
                List<Orders> orders = orderTicket.getOrders();
                synchronized(orders) {
                    for(Orders order : orders) {
                        Food requestedItem = order.item();
                        TimeUnit.MILLISECONDS.sleep(rand.nextInt(500));
                        Plate plate = new Plate(order, requestedItem);
                        order.getOrderTicket().getWaitPerson().
                                filledOrders.put(plate);
                    }
                }
            }
        } catch(InterruptedException e) {
            System.out.println(this + " interrupted");
        }
        System.out.println(this + " off duty");
    }

    public String toString() { return "Chef " + id + " "; }
}

class Restaurants implements Runnable {
    private List<WaitPersons> waitPersons =
            new ArrayList<WaitPersons>();
    private List<Chefs> chefs = new ArrayList<Chefs>();
    private ExecutorService exec;
    private static Random rand = new Random(47);
    final BlockingQueue<OrderTicket> orderTickets =
            new LinkedBlockingQueue<OrderTicket>();
    public Restaurants(ExecutorService e, int nWaitPersons,
                      int nChefs) {
        exec = e;
        for(int i = 0; i < nWaitPersons; i++) {
            WaitPersons waitPerson = new WaitPersons(this);
            waitPersons.add(waitPerson);
            exec.execute(waitPerson);
        }
        for(int i = 0; i < nChefs; i++) {
            Chefs chef = new Chefs(this);
            chefs.add(chef);
            exec.execute(chef);
        }
    }

    public void run() {
        try {
            while(!Thread.interrupted()) {
                WaitPersons wp = waitPersons.get(
                        rand.nextInt(waitPersons.size()));
                int nCustomers = rand.nextInt(4) + 1;
                Table t = new Table(wp, nCustomers, exec);
                exec.execute(t);
                TimeUnit.MILLISECONDS.sleep(400 * nCustomers);
            }
        } catch(InterruptedException e) {
            System.out.println("Restaurant interrupted");
        }
        System.out.println("Restaurant closing");
    }
}




